<?php

require_once dirname(__FILE__).'/../DocParsing/AIS_DocComment.php';
require_once dirname(__FILE__).'/../DocParsing/AIS_DocCommentFunction.php';
require_once dirname(__FILE__).'/../DocParsing/AIS_DocCommentTagParamWithRestrictions.php';

/**
 * This class is used to create Validation functions that can be used to validate
 * an object with a given class type or for the parameters of a function
 *
 */
class AIS_TypeValidationFunctionCreator {
	
	private $typeChecks = array();
	private $arrayChecks = array();
	private $methodChecks = array();
	private static $id = 0;
	private $primitiveChecks = array(
		'int' => 'is_numeric',
		'bool' => 'is_bool',
		'float' => 'is_numeric',
		'double' => 'is_numeric',
		'string' => 'is_string'
	);
	
	private $restrictionChecks = array(
		'int' => array('maximum' => 'setMaximum','minimum' => 'setMinimum'),
		'float' => array('maximum' => 'setMaximum','minimum' => 'setMinimum'),
		'double' => array('maximum' => 'setMaximum','minimum' => 'setMinimum'),
		'string' => array('maxLength' => 'setMaxLength','minLength' => 'setMinLength', 'pattern' => 'setPattern'),
		'array' => array('maxItems' => 'setMaxItems', 'minItems' => 'setMinItems')
	);
	
	public function addClassType($type)
	{
		if (isset($this->typeChecks[$type]))
		{
			return;
		}
		$this->typeChecks[$type] = 1;
		$rc = new ReflectionClass($type);
		$props = $rc->getProperties();
		$checks = array();
		foreach ($props as $property)
		{
			/* @var $property ReflectionProperty */
			$comm = new AIS_DocComment($property->getDocComment());
			/* @var $tag AIS_DocCommentTagParamWithRestrictions */
			$tag = $comm->getTag('@var');
			$valType = $tag->getValue(0);
			$name = $property->getName();
			if (isset($this->primitiveChecks[$valType]))
			{
				$checks[] = $this->generatePrimitiveTypeCheck('$obj->'.$name, $tag);
			}
			elseif (AIS_DocComment::isArray($valType))
			{
				$checks[] = $this->generateArrayTypeCheck('$obj->'.$name, $tag);
			}
			else
			{
				if (!isset($this->typeChecks[$valType]))
				{
					$this->addClassType($valType);
				}
				if ($tag->hasDefault())
				{
					$checks[] = '(('.$name.' == null ? '.$name.' = '.$tag->getDefault().'|| true  : false) || validate_'.$valType.'($obj->'.$name.'))';
				}
				elseif ($tag->isOptional())
				{
					$checks[] = '(('.$name.' == null) || validate_'.$valType.'($obj->'.$name.'))';
				}
				else 
				{
					$checks[] = 'validate_'.$valType.'($obj->'.$name.')';
				}
			}
		}
		$checks = join('&&', $checks);
		$this->typeChecks[$type] = 'function validate_'.$type.'($obj){return '.$checks.';}';
	}
	/**
	 * 
	 *
	 * @param ReflectionMethod $method
	 * @return string
	 */
	public function addMethod(ReflectionMethod $method)
	{
		$class = $method->getDeclaringClass();
		$className = $class->getName();
		$methodName = $method->getName();
		$params = $method->getParameters();
		$comment = new AIS_DocCommentFunction($method->getDocComment());
		$checks = array();
		$paramNames = array();
		$i = 0;
		foreach ($params as $param)
		{
			/* @var $param ReflectionParameter */
			$tag = $comment->getParam('$'.$param->getName());
			$type = $tag->getValue(0);
			if (isset($this->primitiveChecks[$type]))
			{
				$checks[] = $this->generatePrimitiveTypeCheck('$params['.$i.']',$tag);
			}
			else if (AIS_DocComment::isArray($type))
			{
				$checks[] = $this->generateArrayTypeCheck('$params['.$i.']', $tag);
			}
			else 
			{
				$this->addClassType($type);
				$checks[] = 'validate_'.$type.'('.'$params['.$i.']'.')';
			}
			$i++;
		}
		$checks = join('&&',$checks);
		if ($checks == '')
		{
			$checks = 'true';
		}
		$this->methodChecks[] = 'function validate_'.$className.'_'.$methodName.'($params){return '.$checks.';}';
	}
	
	public function getValidationFunctionName(ReflectionMethod $method)
	{
		$class = $method->getDeclaringClass();
		$className = $class->getName();
		$methodName = $method->getName();
		return 'validate_'.$className.'_'.$methodName;
	}
	
	public function generateValidationFunctions($file)
	{
		$handle = fopen($file, 'w');
		fwrite($handle, "<?php\n");
		foreach ($this->arrayChecks as $arChecks)
		{
			fwrite($handle, $arChecks."\n\n");
		}
		foreach ($this->typeChecks as $arChecks)
		{
			fwrite($handle, $arChecks."\n\n");
		}
		foreach ($this->methodChecks as $arChecks)
		{
			fwrite($handle, $arChecks."\n\n");
		}
		fwrite($handle, '?>');
		fclose($handle);
	}
	/**
	 * Generates the checks for the given primitive type to the container
	 *
	 * @param string $name
	 * @param AIS_DocCommentTagParamWithRestrictions $tag
	 */
	private function generatePrimitiveTypeCheck($name, $tag)
	{
		$type = $tag->getValue(0);
		$checks = array();
		$checks[] = $this->primitiveChecks[$type].'('.$name.')';
		$this->addRestrictions($name, $checks, $tag);
		$checks = join('&&',$checks);
		if ($tag->hasDefault())
		{
			return '(('.$name.' == null ? '.$name.' = '.$tag->getDefault().' || true : false) || ('.$checks.'))';
		}
		if ($tag->isOptional())
		{
			return '(('.$name.' == null) || ('.$checks.'))';
		}
		return '('.$checks.')';
	}
	/**
	 * Generates the checks for the given array type to the container
	 *
	 * @param string $name
	 * @param AIS_DocCommentTagParamWithRestrictions $tag
	 */
	private function generateArrayTypeCheck($name, $tag)
	{
		$type = $tag->getValue(0);
		$checks = array();
		$checks[] = '(is_array('.$name.'))';
		$subType = AIS_DocComment::getArrayType($type);
		$functionBody = null;
		if (isset($this->primitiveChecks[$subType]))
		{
			$functionBody = $this->generatePrimitiveTypeCheck('$value', new AIS_DocCommentTagParamWithRestrictions('@param '.$subType.' dummy',2));
		}
		elseif (AIS_DocComment::isArray($subType))
		{
			$functionBody = $this->generateArrayTypeCheck('$value', new AIS_DocCommentTagParamWithRestrictions('@param '.$subType.' dummy',2));
		}
		else 
		{
			$this->addClassType($subType);
			$functionBody = 'validate_'.$subType.'($value)';
		}
		$num = AIS_TypeValidationFunctionCreator::$id;
		$function = 'function validate_array_'.$num.'($array){$res = true; foreach ($array as $value){$res = $res && '.$functionBody.';} return $res;}';
		AIS_TypeValidationFunctionCreator::$id++;
		$this->arrayChecks[] = $function;
		$restrChecks = $this->restrictionChecks['array'];
		$restrictions = $tag->getRestrictions();
		foreach ($restrChecks as $pType => $function)
		{
			if (isset($restrictions[$pType]))
			{
				$this->$function($name,$restrictions[$pType], $checks);
			}
		}
		$checks = join('&&', $checks);
		if ($tag->hasDefault())
		{
			return '(('.$name.' == null ? '.$name.' = '.$tag->getDefault().' || true : false) || ('.$checks.'&&validate_array_'.$num.'('.$name.')))';
		}
		if ($tag->isOptional())
		{
			return '(('.$name.' == null) || ('.$checks.'&&validate_array_'.$num.'('.$name.')))';
		}
		return '('.$checks.'&&validate_array_'.$num.'('.$name.'))';
		
	}
	
	private function addRestrictions($name,&$checks,$tag)
	{
		$type = $tag->getValue(0);
		$restrictions = $tag->getRestrictions();
		$restrChecks = $this->restrictionChecks[$type];
		foreach ($restrChecks as $pType => $function)
		{
			if (isset($restrictions[$pType]))
			{
				$this->$function($name,$restrictions[$pType], $checks);
			}
		}
	}
	
	private function setMaximum($name, $val, &$container)
	{
		array_push($container, '('.$name.' <= '.$val.')');
	}
	
	private function setMinItems($name, $val, &$container)
	{
		array_push($container, '(sizeof('.$name.') >= '.$val.')');
	}
	
	private function setMaxItems($name, $val, &$container)
	{
		array_push($container, '(sizeof('.$name.') <= '.$val.')');
	}
	
	private function setPattern($name, $val, &$container)
	{
		array_push($container, '(preg_match(\''.$val.'\', '.$name.') != 0)');
	}
	
	private function setMinLength($name, $val, &$container)
	{
		array_push($container, '(strlen('.$name.') >= '.$val.')');
	}
	
	private function setMaxLength($name, $val, $container)
	{
		array_push($container, '(strlen('.$name.') <= '.$val.')');
	}
}

?>